package com.yunxin.admin.system.application.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yunxin.admin.system.application.MenuQueryService;
import com.yunxin.admin.system.application.assembler.MenuAssembler;
import com.yunxin.admin.system.application.dto.MenuDTO;
import com.yunxin.admin.system.application.dto.MenuInfoDTO;
import com.yunxin.admin.system.application.dto.OptionDTO;
import com.yunxin.admin.system.application.dto.RouteDTO;
import com.yunxin.admin.system.infra.persistence.entity.query.result.RouteResult;
import com.yunxin.admin.system.infra.common.constant.SystemConstants;
import com.yunxin.admin.system.infra.common.enums.MenuTypeEnum;
import com.yunxin.admin.system.infra.common.enums.StatusEnum;
import com.yunxin.admin.system.infra.persistence.entity.SysMenuDO;
import com.yunxin.admin.system.infra.persistence.mapper.SysMenuMapper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author zero°
 * @since 2023-07-11
 */
@Service
@RequiredArgsConstructor
public class MenuQueryServiceImpl implements MenuQueryService {

    private final SysMenuMapper sysMenuMapper;

    private final MenuAssembler menuAssembler;

    @Override
    public List<MenuDTO> listMenus(String keywords) {
        List<SysMenuDO> menus = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenuDO>()
                .like(CharSequenceUtil.isNotBlank(keywords), SysMenuDO::getName, keywords)
                .orderByAsc(SysMenuDO::getSort)
        );
        //求差集，得到 parentIds 中 menuIds 没有的元素
        List<Long> rootIds = CollUtil.subtractToList(menus.stream().map(SysMenuDO::getParentId).collect(Collectors.toSet()),
                menus.stream().map(SysMenuDO::getId).collect(Collectors.toSet()));
        List<MenuDTO> list = new ArrayList<>();
        for (Long rootId : rootIds) {
            // 递归
            list.addAll(recurMenus(rootId, menus));
        }
        return list;
    }

    @Override
    public List<OptionDTO<Long>> listMenuOptions() {
        List<SysMenuDO> menuList = sysMenuMapper.selectList(new LambdaQueryWrapper<SysMenuDO>().orderByAsc(SysMenuDO::getSort));
        return recurMenuOptions(SystemConstants.ROOT_NODE_ID, menuList);
    }

    /**
     * 路由列表
     */
    @Override
    @Cacheable(cacheNames = "system", key = "'routes'")
    public List<RouteDTO> listRoutes() {
        List<RouteResult> menuList = sysMenuMapper.listRoutes();
        return recurRoutes(SystemConstants.ROOT_NODE_ID, menuList);
    }

    @Override
    public Set<String> listRolePerms(Set<String> roles) {
        return sysMenuMapper.listRolePerms(roles);
    }

    @Override
    public MenuInfoDTO getMenuById(Long id) {
        SysMenuDO sysMenuDO = sysMenuMapper.selectById(id);
        return menuAssembler.toMenuInfoDTO(sysMenuDO);
    }

    /**
     * 递归生成菜单列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return List<MenuResponse>
     */
    private List<MenuDTO> recurMenus(Long parentId, List<SysMenuDO> menuList) {
        if (CollUtil.isEmpty(menuList)) {
            return Collections.emptyList();
        }
        return menuList.stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(entity -> {
                    MenuDTO menuDTO = menuAssembler.toMenuDTO(entity);
                    List<MenuDTO> children = recurMenus(entity.getId(), menuList);
                    menuDTO.setChildren(children);
                    return menuDTO;
                }).toList();
    }

    /**
     * 递归生成菜单路由层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return List<RouteDTO>
     */
    private List<RouteDTO> recurRoutes(Long parentId, List<RouteResult> menuList) {
        return CollUtil.emptyIfNull(menuList).stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu -> {
                    RouteDTO routeDTO = new RouteDTO();
                    MenuTypeEnum menuTypeEnum = menu.getType();
                    if (MenuTypeEnum.MENU.equals(menuTypeEnum)) {
                        // 路由 name 需要驼峰，首字母大写
                        String routeName = StringUtils.capitalize(CharSequenceUtil.toCamelCase(menu.getPath(), '-'));
                        //  根据name路由跳转 this.$router.push({name:xxx})
                        routeDTO.setName(routeName);
                    }
                    // 根据path路由跳转 this.$router.push({path:xxx})
                    routeDTO.setPath(menu.getPath());
                    routeDTO.setRedirect(menu.getRedirect());
                    routeDTO.setComponent(menu.getComponent());

                    RouteDTO.Meta meta = new RouteDTO.Meta();
                    meta.setTitle(menu.getName());
                    meta.setIcon(menu.getIcon());
                    meta.setRoles(menu.getRoles());
                    meta.setHidden(StatusEnum.DISABLE.getValue().equals(menu.getVisible()));
                    meta.setKeepAlive(true);
                    routeDTO.setMeta(meta);
                    List<RouteDTO> children = recurRoutes(menu.getId(), menuList);
                    routeDTO.setChildren(children);
                    return routeDTO;
                }).toList();
    }


    /**
     * 递归生成菜单下拉层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return List<OptionResponse<Long>>
     */
    private List<OptionDTO<Long>> recurMenuOptions(Long parentId, List<SysMenuDO> menuList) {
        return CollUtil.emptyIfNull(menuList).stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(menu -> new OptionDTO<>(menu.getId(), menu.getName(), recurMenuOptions(menu.getId(), menuList)))
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

    }


}
